module.exports = (function(){
    var util = require('util');
    var mysql = require('mysql');
    //纤程
    var Fiber = require('fibers');

    var fields = {};
    var models = {};

    function defineModels(modelConfig) {
        for ( var modelName in modelConfig ) {
            defineModel(modelName, modelConfig[modelName]);
        }
    };

    // class BaseModel
    function BaseModel() {
        this.id = undefined;
    }

    function defineModel(modelName, modelConfig) {
        var modelFields = modelConfig.fields;

        function ModelType(initialValues) {
            BaseModel.call(this);

            var fields = this.config.fields;
            for ( var fieldName in fields ) {
                if ( initialValues && fieldName in initialValues ) {
                    this[fieldName] = initialValues[fieldName];
                }
                else {
                    this[fieldName] = undefined;
                }
            }
        }
        ModelType.prototype = Object.create(BaseModel.prototype);
        ModelType.prototype.model = {
            name: modelName,
            type: ModelType
        }
        ModelType.prototype.config = modelConfig;
        ModelType.prototype.config.fields.id = fields.Integer({
            maxLength: 11
        });
        models[modelName] = ModelType;

        return ModelType;
    };

    // class BaseField {
    ///////////////////////////////////////////////////////////////////////////
    function BaseField(typeName, config) {
        this.typeName = typeName;
        this.config = {};
    }

    BaseField.prototype.fillConfig = function(config) {
        for ( item in config ) {
            this.config[item] = config[item];
        }
    }

    BaseField.prototype.parse = function(text) {
        throw 'Not implemented';
    }
    // }
    ///////////////////////////////////////////////////////////////////////////

    function CharField(config) {
        BaseField.call(this, 'Char', config);
        this.config = {
            maxLength: 50
        };

        if ( config != undefined ) {
            this.fillConfig(config);
        }
    }
    CharField.prototype = Object.create(BaseField.prototype);
    CharField.prototype.parse = function(text) {
        return text;
    }

    fields.Char = function(config) {
        return new CharField(config);
    }

    function IntegerField(config) {
        BaseField.call(this, 'Integer', config);
        this.config = {
            maxLength: 11
        };

        if ( config != undefined ) {
            this.fillConfig(config);
        }
    }
    IntegerField.prototype = Object.create(BaseField.prototype);
    IntegerField.prototype.parse = function(text) {
        return Number(text);
    }

    fields.Integer = function(config) {
        return new IntegerField(config);
    }
    
    function ReferenceField(config) {
        BaseField.call(this, 'Reference', config);
        this.config = {
        }

        this.fillConfig(config);

        if ( !('target' in config) ) {
            throw 'No target of ReferenceField is specified';
        }
        if ( !('type' in config) ) {
            throw 'No type of ReferenceField is specified';
        }

        this._targetModel = null;
    }
    ReferenceField.prototype = Object.create(BaseField.prototype);
    ReferenceField.prototype.parse = function(text) {
        return new this.targetModel({
            id: Number(text)
        });
    }

    Object.defineProperty(ReferenceField.prototype, 'targetModel', {
        get: function() {
            if ( this._targetModel == null ) {
                this._targetModel = models[this.config.target];
            }

            return this._targetModel;
        }
    });

    fields.Reference = function(config) {
        return new ReferenceField(config);
    }

    function Session(pool) {
        this.status = 'CLOSED';
        this.pool = pool;
        this.connection = null;
        this.queryCache = null;
    }

    Session.prototype.begin = function() {
        var fiber = Fiber.current;
        var session = this;

        this.pool.getConnection(function(err, connection) {
            if ( err ) {
                throw err;
            }

            session.status = 'OPEN';
            session.connection = connection;

            fiber.run();
        });

        Fiber.yield();
    }

    // class Session {
    ///////////////////////////////////////////////////////////////////////////
    Session.prototype.end = function() {
        this.status = 'CLOSED';
        this.connection.release();
        this.connection = null;
    }

    Session.prototype.isOpen = function() {
        return this.status == 'OPEN';
    }

    Session.prototype.generateInsertRequest = function(model) {
        var fields = model.config.fields;
        var parameters = {};

        for ( fieldName in fields ) {
            if ( model[fieldName] === undefined ) {
                continue;
            }

            if ( fields[fieldName] instanceof ReferenceField ) {
                parameters[fieldName] = model[fieldName].id;
            }
            else {
                parameters[fieldName] = model[fieldName];
            }
        }

        var insertStatement = util.format('insert into `%s` set ?', model.model.name);

        return {
            statement: insertStatement,
            parameters: parameters
        };
    }

    Session.prototype.add = function(model) {
        var fiber = Fiber.current;
        var successful = false;

        if ( !this.isOpen() ) {
            return false;
        }

        var insertRequest = this.generateInsertRequest(model);
        this.connection.query(insertRequest.statement, 
                insertRequest.parameters,
                function(err, result) {
                    if ( !err ) {
                        model.id = result.insertId;
                        successful = true;
                    }
                    fiber.run();
                }
        );

        Fiber.yield();

        return successful;
    }

    Session.prototype.generateUpdateRequest = function(model) {
        var parameters = {};

        var fields = model.config.fields;
        for ( fieldName in fields ) {
            if ( model[fieldName] === undefined ) {
                continue;
            }

            if ( fields[fieldName] instanceof ReferenceField ) {
                parameters[fieldName] = model[fieldName].id;
            }
            else {
                parameters[fieldName] = model[fieldName];
            }
        }

        var updateStatement = util.format('update `%s` set ? where `id` = ', model.model.name) + 
            this.connection.escape(model.id);

        return {
            statement: updateStatement,
            parameters: parameters
        };
    }

    Session.prototype.save = function(model) {
        if ( !this.isOpen() ) {
            callback('connection closed.');
            return;
        }

        var fiber = Fiber.current;
        var successful = false;

        var updateRequest = this.generateUpdateRequest(model);
        this.connection.query(updateRequest.statement, 
                updateRequest.parameters,
                function(err, result) {
                    if ( !err && result.changedRows > 0 ) {
                        successful = true;
                    }
                    fiber.run();
                }
        );

        Fiber.yield();

        return successful;
    }

    Session.prototype.generateDeleteRequest = function(model) {
        var deleteStatement = util.format('delete from `%s` where `id` = ?', model.model.name);

        return {
            statement: deleteStatement,
            parameters: [ model.id ]
        };
    }

    Session.prototype.remove = function(model) {
        if ( !this.isOpen() ) {
            return false;
        }

        if ( model.id === null || model.id === undefined ) {
            return false;
        }

        var fiber = Fiber.current;
        var successful = false;

        var deleteRequest = this.generateDeleteRequest(model);
        this.connection.query(deleteRequest.statement, 
                deleteRequest.parameters,
                function(err, result) {
                    if ( !err && result.affectedRows > 0 ) {
                        successful = true;
                    }
                    fiber.run();
                }
        );

        Fiber.yield();

        return successful;
    }

    Session.prototype.generateSelectRequest = function(model) {
        var selectStatement = util.format('select * from `%s`', model.model.name);
        var parameters = [];

        var whereClause = ' where';
        var fields = model.config.fields;

        var filterCount = 0;
        for ( fieldName in fields ) {
            if ( model[fieldName] === undefined ) {
                continue;
            }

            var andOperator = filterCount > 0 ? 'and ' : '';
            if ( model[fieldName] != null ) {
                whereClause += util.format(' %s`%s` = ?', andOperator, fieldName );
                if ( fields[fieldName] instanceof ReferenceField ) {
                    parameters.push(model[fieldName].id);
                }
                else {
                    parameters.push(model[fieldName]);
                }
            }
            else {
                whereClause += util.format(' %sisnull(`%s`)', andOperator, fieldName );
            }

            filterCount ++;
        }

        if ( filterCount > 0 ) {
            selectStatement += whereClause;
        }

        return {
            statement: selectStatement,
            parameters: parameters
        };
    }

    Session.prototype.rowToModel = function(row, model) {
        var newModel = new model.model.type();
        var fields = model.config.fields;
        for ( var fieldName in fields ) {
            var field = fields[fieldName];

            if ( row[fieldName] != null ) {
                newModel[fieldName] = field.parse(row[fieldName]);
            }
            else {
                newModel[fieldName] = null;
            }
        }
        newModel.id = row.id;

        return newModel;
    }

    Session.prototype.rowsToModelList = function(rows, model) {
        var modelList = [];
        rows.forEach(function(row) {
            var foundModel = this.rowToModel(row, model);

            modelList.push(foundModel);
        }, this);

        return modelList;
    }

    Session.prototype.executeQuery = function(model, callback) {
        if ( !this.isOpen() ) {
            callback('connection closed.');
            return;
        }

        var selectRequest = this.generateSelectRequest(model);
        this.connection.query(
                selectRequest.statement,
                selectRequest.parameters,
                function(err, rows, fields) {
            if ( err ) {
                callback(err);
                return;
            }

            if ( rows.length <= 0 ) {
                callback(null, null);
                return;
            }

            callback(null, rows);
        });
    }

    Session.prototype.getOne = function(model, config) {
        var createdCache = false;

        if ( this.queryCache == null &&
                config != undefined &&
                'recurisive' in config &&
                config.recurisive === true ) {
            this.queryCache = {};
            createdCache = true;
        }

        var modelName = model.model.name;

        if ( this.queryCache && !(modelName in this.queryCache) ) {
            this.queryCache[modelName] = {}
        }

        if ( this.queryCache && model.id != undefined ) {
            if ( model.id in this.queryCache[modelName] ) {
                if ( createdCache ) {
                    this.queryCache = null;
                }
                return this.queryCache[modelName][model.id];
            }
        }

        var session = this;
        var fiber = Fiber.current;
        var foundModel = null;

        this.executeQuery(model, function(err, rows) {
            if ( !err && rows != null && rows.length > 0 ) {
                foundModel = session.rowToModel(rows[0], model);
                if ( session.queryCache && foundModel.id != undefined ) {
                    session.queryCache[modelName][foundModel.id] = foundModel;
                }
            }

            fiber.run();
        });

        Fiber.yield();

        if ( foundModel != null &&
                config != undefined &&
                'recurisive' in config &&
                config.recurisive === true ) {
            this._getReferences(foundModel, config)
        }

        if ( createdCache ) {
            this.queryCache = null;
        }

        return foundModel;
    }

    Session.prototype._getReferences = function(model, config) {
        var fields = model.config.fields;
        for ( var fieldName in fields ) {
            var field = fields[fieldName];
            if ( field instanceof ReferenceField ) {
                if ( field.config.type == "n1" || field.config.type == "11" ) {
                    model[fieldName] = this.getOne(model[fieldName], config);
                }
            }
        }
    }

    Session.prototype.get = function(model, config) {
        var createdCache = false;

        if ( this.queryCache == null &&
                config != undefined &&
                'recurisive' in config &&
                config.recurisive === true ) {
            this.queryCache = {};
            createdCache = true;
        }

        var modelName = model.model.name;

        if ( this.queryCache && !(modelName in this.queryCache) ) {
            this.queryCache[modelName] = {}
        }

        var session = this;
        var fiber = Fiber.current;
        var foundModels = null;

        this.executeQuery(model, function(err, rows) {
            if ( !err && rows != null && rows.length > 0 ) {
                foundModels = session.rowsToModelList(rows, model);
            }

            fiber.run();
        });

        Fiber.yield();

        if ( foundModels != null && 
            config != undefined &&
            'recurisive' in config &&
            config.recurisive === true ) {
            for ( var modelIndex in foundModels ) {
                var foundModel = foundModels[modelIndex];
                if ( this.queryCache && foundModel.id != undefined ) {
                    if ( foundModel.id in this.queryCache[modelName] ) {
                        foundModels[modelIndex] = this.queryCache[modelName][foundModel.id]
                    }
                    else {
                        this.queryCache[modelName][foundModel.id] = foundModel;
                        this._getReferences(foundModel, config)
                    }
                }
            }
        }

        if ( createdCache ) {
            this.queryCache = null;
        }

        return foundModels;
    }

    Session.prototype.getAll = function(modelType, config) {
        var model = new modelType();
        return this.get(model, config);
    }
    // }
    ///////////////////////////////////////////////////////////////////////////

    // class SessionFactory {
    ///////////////////////////////////////////////////////////////////////////
    function SessionFactory(config) {
        this.pool = mysql.createPool(config);
    }

    SessionFactory.prototype.createSession = function() {
        return new Session(this.pool);
    }
    // }
    ///////////////////////////////////////////////////////////////////////////

    return {
        defineModel: defineModel,
        fields: fields,
        SessionFactory: SessionFactory
    };
}());

